<?PHP
/*
 * $Id: barge-io.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\io;



use \DateTime;
use \Directory;
use \Exception;

use barge\ClassLoader;
use barge\System;
use barge\utils\String;
use barge\utils\NestedException;

/**
 * @version 1.0 2009/04/03
 * @see NestedException
 */
class IOException extends NestedException {
}

/**
 * @version 1.0 2009/04/03
 * @see NestedException
 */
class FileNotFoundException extends NestedException {
}

/**
 * @version 1.0 2009/04/03
 */
interface InputStream
{
	/**
	 * ファイルを閉じます
	 */
	public function close();
}

/**
 * ファイル読み込み
 * @version 1.0
 * @see InputStream
 */
class FileInputStream implements InputStream
{
	/**
	 * @var resource
	 */
	protected $fp;

	/**
	 * コンストラクタ
	 *
	 * @param string $file
	 * @throws FileNotFoundException ファイルが存在しないまたは、なんらかの理由で開くことが
	 * 					できない場合にスローされます
	 */
	public function __construct($url)
	{
		$this->fp = @fopen($url, 'r');
		if ($this->fp === false) {
			$message = "ファイル {$url} は存在しないか、開くことができません。";
			throw new FileNotFoundException($message);
		}
	}

	/**
	 * ファイルポインターをファイルの先頭に移動します
	 */
	public function reset()
	{
		 return fseek($this->fp, 0, SEEK_SET);
	}

	/**
	 * ファイルポインターを現在の位置から指定オフセット移動します
	 * @parma int $pos
	 * @return void
	 */
	public function skip($pos)
	{
		 return fseek($this->fp, $pos, SEEK_CUR);
	}

	/**
	 * ファイルポインターを指定位置にセットします
	 * @parma int $pos
	 */
	public function seek($pos)
	{
		 return fseek($this->fp, $pos, SEEK_SET);
	}

	/**
	 * @param int $length
	 * @return string
	 */
	public function read($length = 1)
	{
		 return fread($this->fp, $length);
	}

//	/**
//	 * @param int $length
//	 * @return string
//	 */
//	public function readLine($length = -1)
//	{
//		 $line = stream_get_line($this->fp, $length, "\n");
//		 return rtrim($line);
//	}

	/**
	 * @param int $length
	 * @return string
	 */
	public function readAll()
	{
		 return stream_get_contents($this->fp);
	}

	/**
	 * 現在のファイルポインター位置から改行コードまでを読み込みます。
	 * ファイルの終端に達した場合、このメソッドは<code>null</code>を返します。
	 *
	 * <pre>
	 * while (($buf = $reader->readLine()) !== null) {
	 *     echo $buf;
	 * }
	 * </pre>
	 *
	 * @param int $length
	 * @parma bool $delimiter デリミータを戻り値に含めるかどうか
	 * @return string
	 * @see #fgets
	 */
	public function readLine($length = null, $delimiter = false)
	{
		$ret = ($length !== null ?
					fgets($this->fp, $length) :
					fgets($this->fp));
		if ($ret !== false) {
			return $delimiter ? $ret : rtrim($ret, "\r\n");
		} else {
			return null;
		}
	}

	/**
	 * ファイルを閉じます
	 */
	public function close()
	{
		fclose($this->fp);
		$this->fp = null;
	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function __destruct()
	{
		if ($this->fp !== null) {
			fclose($this->fp);
			$this->fp = null;
		}
	}

}

/**
 * @since 1.0 2009/03/17
 */
interface OutputStream
{
	/**
	 * @param string $content
	 * @return int
	 */
	public function write($content);

	/**
	 * @since 1.0 2009/03/17
	 */
	public function flush();

	/**
	 * @since 1.0 2009/03/17
	 */
	public function close();
}

/**
 * @since 1.0 2009/03/17
 * @see OutputStream
 */
class FileOutputStream implements OutputStream
{
	/**
	 * @var resource
	 */
	private $fp;

	/**
	 * 改行コード
	 *
	 * @var string
	 */
	private $linefeed = "\n";

	/**
	 * エンコーディング
	 * @var string
	 */
	private $encoding;

	/**
	 * コンストラクタ
	 *
	 * @param string $file
	 * @param bool $append
	 * @param string $encoding 出力エンコーディング
	 * @throws FileNotFoundException ファイルが存在しないまたは、なんらかの理由で開くことが
	 * 					できない場合にスローされます
	 */
	public function __construct($path, $append = false, $encoding = null)
	{
		$this->encoding = $encoding;
		$this->fp = @fopen($path, 'w'.($append ? '+':''));
		if ($this->fp === false) {
			$message = "ファイル {$path} は存在しないか、開くことができません。";
			throw new FileNotFoundException($message);
		}
	}

	/**
	 * @param string $content
	 */
	public function setLinefeed($feed)
	{
		 $this->linefeed = $feed;
	}

	/**
	 * @param string $content
	 */
	public function write($content)
	{
		$begin = ftell($this->fp);
		fwrite($this->fp,
		 	($this->encoding !== null ?
		 		mb_convert_encoding((string)$content, $this->encoding, 'utf-8') :
		 		(string)$content));
		return ftell($this->fp) - $begin;
	}

//	/**
//	 * @param string $content
//	 * @return int
//	 */
//	public function writeln($content)
//	{
//		$begin = ftell($this->fp);
//		fwrite($this->fp,
//		 	($this->encoding !== null ?
//		 		mb_convert_encoding((string)$content, $this->encoding, 'utf-8') :
//		 		(string)$content));
//		fwrite($this->fp, $this->linefeed);
//		return ftell($this->fp) - $begin;
//	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function flush()
	{
		 fflush($this->fp);
	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function close()
	{
		fclose($this->fp);
		$this->fp = null;
	}

	/**
	 * @since 1.0 2009/03/17
	 * @return int
	 */
	public function tell()
	{
		return ftell($this->fp);
	}

	/**
	 * @param int $pos
	 * @since 1.0 2009/03/17
	 */
	public function seek($pos)
	{
		fseek($this->fp, $pos, SEEK_SET);
	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function __destruct()
	{
		if ($this->fp !== null) {
			fclose($this->fp);
			$this->fp = null;
		}
	}

}

/**
 * @since 1.0 2009/03/17
 * @see FileOutputStream
 */
class BufferedOutputStream extends FileOutputStream
{
	private $size = 1024;
	private $buffer = '';

	/**
	 * @param int $size
	 */
	public function setBufferSize($size)
	{
		$this->size = $size;
	}

	/**
	 * @param string $content
	 */
	public function write($content)
	{
		$this->buffer .= $content;
		if (strlen($this->buffer) > $this->size) {
			parent::write($this->buffer);
			$this->buffer = null;
		}
	}

	/**
	 * @param string $content
	 */
	public function flush()
	{
		parent::write($this->buffer);
		parent::flush();
		$this->buffer = null;
	}

	/**
	 * @param string $content
	 */
	public function close()
	{
		parent::write($this->buffer);
		parent::close();
		$this->buffer = null;
	}

//	/**
//	 * @param string $content
//	 * @return int
//	 */
//	public function writeln($content)
//	{
//		$this->buffer .= $content;
//		if (strlen($this->buffer) > $this->size) {
//			parent::write($this->buffer);
//			$this->buffer = null;
//		}
//	}
}

/**
 * @since 1.0 2009/03/17
 * @see OutputStream
 */
class EchoOutputStream implements OutputStream
{
	/**
	 * @var string
	 */
	private $encoding;

	/**
	 * 改行コード
	 *
	 * @var string
	 */
	private $linefeed = "\n";

	/**
	 * コンストラクタ
	 *
	 * @param string $file
	 * @param bool $append
	 * @param string $encoding 出力エンコーディング
	 * @throws FileNotFoundException ファイルが存在しないまたは、なんらかの理由で開くことが
	 * 					できない場合にスローされます
	 */
	public function __construct($encoding)
	{
		$this->encoding = $encoding;
	}

	/**
	 * @param string $content
	 */
	public function setLinefeed($feed)
	{
		 $this->linefeed = $feed;
	}

	/**
	 * @param string $content
	 */
	public function write($content)
	{
		echo ($this->encoding !== null ?
		 		mb_convert_encoding((string)$content, $this->encoding, 'utf-8') :
		 		(string)$content);
//		flush();
		return 0;
	}

//	/**
//	 * @param string $content
//	 * @return int
//	 */
//	public function writeln($content)
//	{
//		echo ($this->encoding !== null ?
//		 		mb_convert_encoding((string)$content, $this->encoding, 'utf-8') :
//		 		(string)$content).$this->linefeed;
//		return 0;
//	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function flush()
	{
		flush();
	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function close()
	{
	}

	/**
	 * @since 1.0 2009/03/17
	 */
	public function __destruct()
	{
	}

}

/**
 * ファイルクラス（PHPファイル関数のラッパー）
 *
 * @version 1.1 2006/10/21
 * @author Kenji Namba
 * @since Barge 1.1 2006/09/11
 */
class File
{
	/**
	 * Private
	 */
	private function __construct()
	{
	}

	/**
	 * ディレクトリーを作成します
	 * @param string $path
	 * @since 1.0 2006/09/11
	 * @throws IOException ディレクトリの作成に失敗した場合
	 */
	public static function makeDir($path, $recursive = false)
	{
		$permision = System::firstNotEmpty(System::getProperty('File/directoryPermission'), 755);
		$ret = @mkdir($path, intval($permision), $recursive);
		if ($ret === false) {
			$message = String::format('ディレクトリーを作成できません。path={0} : {1}', $path, @$php_errormsg);
			throw new IOException($message);
		}
	}

	/**
	 * ファイルが存在するかどうかを返します
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.1 2006/10/23
	 */
	public static function exists($path)
	{
		return file_exists($path);
	}

	/**
	 * ファイルが存在しないかどうかを返します
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.1 2006/10/23
	 */
	public static function notExists($path)
	{
		return !self::exists($path);
	}

	/**
	 * ファイルを削除します。ファイルが存在しない場合は例外が発生します
	 *
	 * @param string $path
	 * @since 1.0 2006/09/12
	 * @exception IOException 削除対象ファイルがない、削除できない
	 */
	public static function unlink($path)
	{
		if (@unlink($path) === false) {
			$message = String::format('ファイルを削除できません。path={0} : {1}', $path, @$php_errormsg);
			throw new IOException($message);
		}
	}

	/**
	 * ファイルを削除します。ファイルが存在しない場合は例外が発生します
	 *
	 * @param string $path
	 * @since 1.0 2006/09/12
	 * @see #unlink
	 */
	public static function deleteFile($path)
	{
		self::unlink($path);
	}

//	/**
//	 * ファイルを削除します。ファイルが存在しない場合は例外が発生します
//	 *
//	 * @param string $path
//	 * @since 1.0 2006/09/12
//	 * @exception FileNotFoundException ファイルが存在しない
//	 */
//	public function unlinkWildcard(PathWildcard $wildCard)
//	{
////		if (!$this->exists()) {
////			throw new FileNotFoundException($path);
////		}
////		unlink($path);
//	}

	/**
	 * ディレクトリを削除します。ファイルが存在しない場合は例外が発生します
	 *
	 * @param string $path
	 * @param bool $recursive 再帰的にディレクトリの中身を削除するか
	 * @since 1.0 2006/09/12
	 * @exception FileNotFoundException ファイルが存在しないまたはディレクトリでない
	 */
	public static function removeDir($path, $recursive = false)
	{
		if (!is_dir($path)) {
			throw new FileNotFoundException($path);
		}
		if ($recursive) {
			$directory = dir($path);
			while (($file = $directory->read()) !== false) {
				if ($file[0] === '.') continue;
				$childPath = $path.'/'.$file;
				if (is_dir($childPath)) {
					self::removeDir($childPath, $recursive);
				} else {
					unlink($childPath);
				}
			}
			$directory->close();
		}

		if (@rmdir($path) === false) {
			throw new FileNotFoundException($path);
		}
	}

	/**
	 * ディレクトリーの中身を削除します。指定されたディレクトリー自体は削除されずに
	 * 残るため、ディレクトリーの中身をクリアしたい場合に利用できます。
	 *
	 * @param string $path
	 * @param bool $recursive 再帰的に子ディレクトリの中身を削除するか
	 * @since 1.0 2006/09/12
	 * @exception FileNotFoundException ファイルが存在しないまたはディレクトリでない
	 */
	public static function removeFiles($path, $recursive = false)
	{
		if (!is_dir($path)) {
			throw new FileNotFoundException($path);
		}

		$directory = dir($path);
		while (($file = $directory->read()) !== false) {
			if ($file[0] === '.') continue;
			$childPath = $path.'/'.$file;
			if (is_dir($childPath)) {
				if ($recursive) {
					self::removeFiles($childPath, $recursive);
					rmdir($childPath);
				} else {
					throw new FileNotFoundException($path);
				}
			} else {
				unlink($childPath);
			}
		}
		$directory->close();
	}

	/**
	 * ファイルサイズを取得します
	 *
	 * @param string $path
	 * @return int バイト単位のファイルサイズ
	 * @since 1.0 2006/09/12
	 * @exception IOException ファイルが存在しない
	 */
	public static function getFileSize($path)
	{
		$size = @filesize($path);
		if ($size === false) {
			$message = String::format('ファイルにアクセスできません。path={0} : {1}', $path, @$php_errormsg);
			throw new IOException($message);
		}
		return $size;
	}

	/**
	 * ファイルがディレクトリーかどうかを返します。存在しないまたはアクセスできない
	 * ファイルに対しても<code>false</code>の結果を返します。
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.0 2006/09/12
	 */
	public static function isDir($path)
	{
		return is_dir($path);
	}

	/**
	 * ファイルがリンクかどうかを返します。存在しないまたはアクセスできない
	 * ファイルに対しても<code>false</code>の結果を返します。このメソッドは、
	 * Windows 環境では正しく動作しない可能性があります。
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.0 2006/09/12
	 */
	public static function isLink($path)
	{
		return is_link($path);
	}

	/**
	 * 指定パス上に存在するオブジェクトが一般的なファイルかどうかを返します。
	 * 存在しないまたはアクセスできないファイルに対しても<code>false</code>の結果を返します。このメソッドは、
	 * Windows 環境では正しく動作しない可能性があります。
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.3 2009/06/18
	 */
	public static function isFile($path)
	{
		return is_file($path);
	}

	/**
	 * 指定されたファイル名が書き込み可能かどうかを返す
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.3 2009/06/18
	 */
	public static function isWritable($path)
	{
		return is_writable($path);
	}

	/**
	 * 指定されたファイル名が読み込み可能かどうかを返す
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.3 2009/06/18
	 */
	public static function isReadable($path)
	{
		return is_readable($path);
	}

	/**
	 * 指定されたファイル名が読み込み不可能かどうかを返す
	 *
	 * @param string $path
	 * @return bool
	 * @since 1.3 2009/06/18
	 */
	public static function isNotReadable($path)
	{
		return self::isReadable($path);
	}

	/**
	 * @param string $path
	 * @return DateTime
	 * @since 1.2 2008/10/18
	 */
	public static function getLastModified($path)
	{
		$timst = @filemtime($path);
		if ($timst === false) {
			throw new FileNotFoundException($path);
		}
		$d = new DateTime();
		$d->setTimestamp($timst);
		return $d;
	}

	/**
	 * @param string $path
	 * @return int
	 * @since 1.2 2008/10/18
	 */
	public static function getPermission($path)
	{
		// TODO : HEXコード
		return fileperms($path);
	}

	/**
	 * @param string $path
	 * @return string
	 * @since 1.0 2006/10/18
	 * @throws IOException ファイルを読み込めない
	 */
	public static function getContents($path)
	{
		$contents = @file_get_contents($path);
		if ($contents === false) {
			$message = String::format("ファイル '{0}' を読み込めませんでした。 : {1}", $path, @$php_errormsg);
			throw new IOException($message);
		}
		return $contents;
	}

	/**
	 * 指定ファイルにデータを書き込みます
	 *
	 * @param string $path
	 * @param string $contents
	 * @param bool $append すでにファイルが存在する場合に、追加書き込みを行うかどうか
	 * @since 1.0 2006/10/18
	 */
	public static function putContents($path, $contents, $append = false)
	{
		if (@file_put_contents($path, $contents, $append ? FILE_APPEND : 0) === false) {
			$message = "ファイル '{$path}' を書き込みできませんでした。";
			throw new FileNotFoundException($message);
		}
	}

	/**
	 * ファイルの拡張子を小文字に変換して返します。
	 * 拡張子が見つからない場合は、<code>null<code>が返ります。
	 *
	 * @param string $path
	 * @return string
	 * @since 1.0 2006/10/21
	 */
	public static function getExtName($path)
	{
		$file_name = basename($path);
		$i = 0;
		$ext = null;
		while ($i = strpos($path, '.', $i)) {
			$i++;
			$ext = substr($path, $i); // 末尾まで
		}
		return ($ext !== null ? strtolower($ext) : null);
	}

	/**
	 * ファイルパス中のファイル名またはディレクトリ名を返します
	 *
	 * @param string $filepath
	 * @return string ファイル名
	 */
	public static function getBaseName($filepath)
	{
		return $filepath !== null ? basename($filepath) : null;
	}

	/**
	 * ファイルパス中のディレクトリ名を返します
	 * @param string $filepath
	 * @return string ディレクトリ名
	 */
	public static function getDirName($filepath)
	{
		return $filepath !== null ? dirname($filepath) : null;
	}

	/**
	 * @param string $path
	 * @param DateTime $time 設定するタイムスタンプ、省略された場合は現在のタイムスタンプ
	 */
	public static function touchModified($path, DateTime $time = null)
	{
		$ret = @touch($path, $time !== null ? intval($time->format('U')) : time());
		if ($ret === false) {
			throw new FileNotFoundException($path);
		}
	}

	/**
	 * ファイルパスを連結する
	 * @param ...
	 * @return string
	 */
	public static function concatPath(/*...*/)
	{
		$args = func_get_args();
		$ret = null;
		foreach ($args as $path) {
			if ($ret !== null && !in_array(substr($ret, -1, 1), array('/', '\\'))) {
				$ret .= '/';
			}
			$ret .= $path;
		}
		return $ret;
	}

	/**
	 * ファイルの所有者を表すユーザーIDを返します
	 * @param string $path
	 * @return int
	 * @since 1.3 2009/06/18
	 */
	public static function getOwner($path)
	{
		return fileowner($path);
	}

	/**
	 * ファイルの所有者グループを表すグループIDを返します
	 * @param string $path
	 * @return int
	 * @since 1.3 2009/06/18
	 */
	public static function getGroup($path)
	{
		return filegroup($path);
	}

	/**
	 * ファイルまたはディレクトリ名を変更します
	 *
	 * @param string $ordpath
	 * @param string $newpath
	 * @since 1.3 2009/06/18
	 * @throws IOException ファイルが存在しないまたはアクセス権限なし
	 */
	public static function rename($ordpath, $newpath)
	{
		if (@rename($ordpath, $newpath) === false) {
			$message = "Counld not rename file {$ordpath} to {$newpath}";
			throw new IOException($message);
		}
	}

	/**
	 * ディレクトリーの内容を再帰的にコピーします。
	 *
	 * @param string $ordpath
	 * @param string $newpath
	 * @parma bool $override すでにコピー
	 * @since 1.3 2009/06/18
	 * @throws IOException ファイルが存在しないまたはアクセス権限なし
	 */
	public static function copyDir($ordpath, $newpath, $override = false)
	{
		if (!is_dir($ordpath)) {
			$message = "Is not a directory path {$ordpath}.";
			throw new IOException($message);
		}

		if (!file_exists($newpath)) {
			if (@mkdir($newpath) === false) {
				$message = "Failed to create directory : {$childnewpath}";
				throw new IOException($childnewpath);
			}
		}

		$directory = dir($ordpath);
		while (($file = $directory->read()) !== false) {
			if ($file[0] === '.') continue;
			$childpath = $ordpath.'/'.$file;
			$childnewpath = $nowpath.'/'.$file;
			if (is_dir($childpath)) {
				self::copyDir($childpath, $childnewpath, $override);
			} else {
				if (@copy($childpath, $childnewpath) === false) {
					$message = "Failed to copy resources {$srcpath} to {$distpath}";
					throw new IOException($message);
				}
			}
		}
		$directory->close();
	}

	/**
	 * ファイルをコピーします、ディレクトリーには使用できません。既にコピー先のファイルが存在
	 * する場合は、上書きされます。
	 *
	 * @param string $srcpath
	 * @param string $distpath
	 * @since 1.3 2009/06/18
	 * @throws IOException ファイルが存在しないまたはアクセス権限なし、変更先のディレクトリが
	 * 			既に存在している場合など
	 */
	public static function copyFile($srcpath, $distpath)
	{
		if (@copy($srcpath, $distpath) === false) {
			$message = "Failed to copy resources {$srcpath} to {$distpath}";
			throw new IOException($message);
		}
	}

	/**
	 * @param string $path
	 * @return FileInputStream
	 */
	public static function getInputStream($path)
	{
		return new FileInputStream($path);
	}

	/**
	 * @param $path
	 * @return FileOutputStream
	 */
	public static function getOutoutStream($path)
	{
		return new FileOutputStream($path);
	}
}




